使用 Vue3 + TailwindCSS 编写组件样式时遇到的问题

#前言

本文写于 TailwindCSS V4 + Vite + Vue3 环境

我之所以使用 TailwindCSS 编写组件又使用 @apply 将样式写在 css 文件而不是写在 class 属性内原因有两点

  1. 方便覆盖,如果写在 class 属性内只能通过引入 tailwind-merge 来动态解析哪些冲突,这是运行时的行为,感觉会多出一比无意义的性能开销
  2. 解决 class 太长问题,在组件中写 tailwindcss 常见的就是 class 名太长太多了,非常混乱还超过了 printwidth 80,阅读及其难受

为什么不放弃使用 tailwindcss

懒,能少写一点是一点,顺便用 tailwindcss 定义好的颜色组等

Vue 支持内联 CSS 因此组件的样式基本写在 <style> 标签内

#样式覆盖问题

组件定义样式后,用户使用 TailwindCSS 样式自定义组件某些样式时会无法覆盖,因为其优先级会低于组件样式的优先级。而使用 TailwindCSS 时除了使用 important 或写 CSS 文件基本无法解决,因此需要编写组件时降低样式的优先级

降低优先级的解决方案:

#CSS where 选择器

使用 where 选择器 (MDN 文档) 可以始终将优先级设置为 0,比如

:where(.ui-button) {
    @apply inline-flex;
}

非常合适用于避免和用户自定义样式冲突

#CSS layer 特性

layer 是 css 的一个实验性特性 (MDN 文档)。

其作用用于组织样式到不同的层中,layer 会按照声明定义顺序,后声明的 layer 优先级会高于前面的。

使用 Tailwind 基本都会用到 layer, Tailwind 中默认定义了 4 种 Layer,分别是 themebasecomponentsutilities

因此可以使用 components layer 来定义组件样式

@layer components{
    :where(.ui-button) {
        @apply inline-flex;
    }
}

这样就能确保该组件不会和用户自定义样式冲突,就算没有 where 选择器也不会和 utilities 层的例如 px-2 等起冲突。

使用 layer 就必需确保 layer 顺序正确,然而很不幸被打乱了

#layer 顺序被打乱问题

TailwindCSS 所使用的 Layer 顺序可以在 tailwindcss/index.css 第一行看到

@layer theme, base, components, utilities;

因此顺序便是

theme -> base -> components -> utilities

像常见的 p-2flex 等都是定义在 utilities layer 中的,优先级是这几个 layer 中最高的

在 Vue 中使用 style 标签默认会在组件挂载时将样式注入到 <head> 末尾,这就可能导致组件样式会比 global.css 提前注入到 head 中。

比如 toast 这种常见于全局挂载的组件,当这种组件中使用了 components layer 就会导致 layer 顺序变成了

components -> theme -> base -> utilities

这就会导致某些样式比如 tailwindcss 中使用 base 层的 prefight 覆盖了组件样式,如上示例可以看到打乱后的 base 层比 components 层高。

为什么会出现这种情况,其实是加载顺序的问题,通常导入 CSS 语句写在导入末尾的,例如

import { createApp } from 'vue';
import App from './pages/app.vue';
import "./global.css"

createApp(App).mount('#app');

App 组件会先于 global.css 加载,而在 App 中使用的全局组件则会先一步被注入到 <head> 标签内

为了解决该问题,只能通过确保 global.css 优先加载比如将其置顶

import "./global.css";
import { createApp } from 'vue';
...

或者将 layer 声明内联定义在入口 HTML head 标签内

<head>
  <style>
    @layer base, components, utilities;
  </style>
</head>

也可以在入口 HTML 直接引入 global.css 文件

<head>
  <link rel="stylesheet" type="text/css" href="/src/global.css"/>
</head>
1019 Words